#include "protocol.h"


pro_buff_t pro_buff;//数据包收发缓存结构体

uint8_t fileID[2]={0};//上一次选择的目录ID
uint8_t auth_flag=0;//外部认证标志

//==============================================
//异或校验
//==============================================
static uint8_t pro_cal_bcc(void)
{
	uint8_t i, bcc=0;
	
	bcc ^= PRO_HEAD;
	bcc ^= PRO_ADDR;
	bcc ^= pro_buff.command;
	bcc ^= pro_buff.length;
	bcc ^= PRO_END;
	for(i=0;i<pro_buff.length;i++)
	{
		bcc ^= *(pro_buff.data+i);
	}
	return bcc;
}

//==============================================
//数据帧接收分解，放在串口中断中处理，不要做复杂
//的数据处理，不然容易死机
//==============================================
void pro_usart_res(uint8_t res)
{
	static uint8_t status=0, count=0;
	
	if(pro_buff.res_flag >= 1) return;
	
	if(status == 0)//判断帧头
	{
		if(res == PRO_HEAD) status =1;
	}
	else if(status == 1)//判断模块地址
	{
		if(res == PRO_ADDR) status =2;
		else status = 0;
	}
	else if(status == 2)//保存命令
	{
		pro_buff.command = (pro_cmd_t)res;
		status = 3;
	}
	else if(status == 3)//保存数据长度
	{
		if(res > 122)//确保整个数据包的长度小于128字节
		{
			status = 0;
			pro_buff.res_flag = 2;//如果不是帧尾表示长度错误
			return;
		}
		pro_buff.length = res;
		status = 4;
		count=0;
	}
	else if(status == 4)//保存数据
	{
		if(count == pro_buff.length)//count是从0开始计算，当等于数据长度表示数据最后一个字节的下一个字节，也就是帧尾
		{
			if(res == PRO_END)
				status = 5;
			else 
			{
				status = 0;
				pro_buff.res_flag = 2;//如果不是帧尾表示长度错误
			}			
		}
		else
			pro_buff.data[count++] = res;
	}
	else if(status == 5)//保存校验位
	{
		status = 0;
		pro_buff.bcc = res;
		pro_buff.res_flag = 1;
	}
}

//==============================================
//数据帧打包发送
//==============================================
void pro_usart_send(void)
{
	uint8_t i;
	uint8_t *pb = NULL, *ph = NULL;
	pb = malloc(pro_buff.length+6);//数据长度加帧头尾地址命令数据长度校验6个字节
	ph = pb;//保存数据帧头部地址，用于释放内存

	*pb++ = PRO_HEAD;
	*pb++ = PRO_ADDR;
	*pb++ = pro_buff.command;
	*pb++ = pro_buff.length;
	memcpy(pb, pro_buff.data, pro_buff.length);
	pb += pro_buff.length;
	*pb ++ = PRO_END;
	*pb = pro_cal_bcc();
	
#if PRO_DEBUG
	printf(">>> RX : ");
#endif
	
	for(i=0;i<pro_buff.length+6;i++)
	{	
#if PRO_DEBUG
		printf("%02x ", *(ph+i));
#else
		USART_SendData(USART2,*(ph+i));
		while(USART_GetFlagStatus(USART2, USART_FLAG_TC) == RESET);
#endif
	}
	
#if PRO_DEBUG
		printf("\r\n");
#endif
	
	free(ph);
}

//==============================================
//数据帧命令任务处理
//==============================================
void pro_task(void)
{
	static uint8_t sak=0;	
	if(pro_buff.res_flag == 1)//表示可以执行命令任务
	{		
#if PRO_DEBUG
		uint8_t i;
		printf("\r\n<<< TX : aa 01 %02x %02x ", pro_buff.command, pro_buff.length);
		for(i=0;i<pro_buff.length;i++)
		{
			printf("%02x ", pro_buff.data[i]);
		}
		printf("bb %02x\r\n", pro_buff.bcc);
#endif
		
		if(pro_buff.bcc != pro_cal_bcc())//首先判断校验
		{
			pro_buff.length = 1;
			pro_buff.data[0] = PRO_ERR_BCC;
		}
		else
		{
			switch (pro_buff.command)
			{
				case GET_VER:
					pro_buff.length = 22;
					pro_buff.data[0] = PRO_OK;
					memcpy(&pro_buff.data[1], (uint8_t *)"YJY-a V1.01 2023-9-27", 21);
					break;
				case GET_UID:
					get_uid(&sak);
					break;
				case STOP:
					stop(sak);
					break;
				case OUT_AUTHENT:
					out_authent(pro_buff.data);
					break;
				case CART_INIT:
					card_init(pro_buff.data);
					break;
				case CART_DEL:
					card_delete(pro_buff.data);
					break;
				case READ_FILE:
					read_file(pro_buff.data);
					break;
				case WRITE_FILE:
					write_file(pro_buff.data);
					break;
				case MOD_KEY:
					mod_key(pro_buff.data);
					break;
				case USER_CMD:
					user_cmd(pro_buff.data);
					break;
				default:
					pro_buff.length = 1;
					pro_buff.data[0] = PRO_NEXIT_CMD;//命令不存在
					break;
			}
		}
		pro_usart_send();//命令数据都存放在数据缓存结构体中，最后一步发送模块返回的命令操作状态和数据
		pro_buff.res_flag = 0;//允许接收
		USART_RX_STA=0;
	}
	else if(pro_buff.res_flag == 2)//长度错误
	{
		pro_buff.length = 1;
		pro_buff.data[0] = PRO_ERR_LENGTH;
		pro_usart_send();
		pro_buff.res_flag = 0;
		USART_RX_STA=0;
	}
}

static uint8_t pro_check_para(uint8_t len)
{
	if(len != pro_buff.length)
	{
		pro_buff.data[0] = PRO_ERR_PARA;
		pro_buff.length = 1;
		return 1;
	}
	else return 0;
}


//==============================================
//选择目录或文件，注意不能选择密钥文件，密钥文件ID是00 00 
//==============================================
static void pro_select_file(uint8_t *temp)
{
	int8_t status = MI_EMPTY;
	pcd_cmd_t pc;
	
	pro_buff.length = 1;
	
	if((fileID[0] != *temp) || (fileID[1] != *(temp+1)))//判断当前文件ID是否和上次的一样，如果一样就不用选，因为每次选完都要外部认证，有点消耗算力
	{
		pc.length = 7;
		pc.buff[0] = 0x00;
		pc.buff[1] = 0xa4;
		pc.buff[2] = 0x00;
		pc.buff[3] = 0x00;
		pc.buff[4] = 0x02;
		pc.buff[5] = *temp;
		pc.buff[6] = *(temp+1);
		
		status = pcd_cmd_transceive(&pc);
		
		if(status == MI_OK)
		{
			if(((uint16_t)pc.buff[pc.length-2]<<8 | pc.buff[pc.length-1]) == 0x6a82)//目录或文件不存在
			{
				pro_buff.data[0] = PRO_NEXIT_DIR;
				return;
			}
			else if(((uint16_t)pc.buff[pc.length-2]<<8 | pc.buff[pc.length-1]) == 0x9000)//选择成功
			{
				auth_flag = 0;//外部认证标志清零需要重新外部认证
				memcpy(fileID, temp, 2);//保存这次的文件ID
				pro_buff.data[0] = PRO_OK;
				return;
			}
		}
		pro_buff.data[0] = PRO_FAIL;
		return;
	}
	pro_buff.data[0] = PRO_OK;
}

//==============================================
//擦除目录下所有文件
//==============================================
static void pro_delete(void)
{
	int8_t status = MI_EMPTY;
	pcd_cmd_t pc;
	
	pc.length = 5;
	pc.buff[0] = 0x80;
	pc.buff[1] = 0x0e;
	pc.buff[2] = 0x00;
	pc.buff[3] = 0x00;
	pc.buff[4] = 0x00;
	
	status = pcd_cmd_transceive(&pc);
	
	pro_buff.length = 1;
	if(status == MI_OK)
	{
		if(((uint16_t)pc.buff[pc.length-2]<<8 | pc.buff[pc.length-1]) == 0x6982)//权限不足
		{
			pro_buff.data[0] = PRO_ERR_LIMIT;
			return;
		}
		else if(((uint16_t)pc.buff[pc.length-2]<<8 | pc.buff[pc.length-1]) == 0x9000)//擦除成功
		{
			pro_buff.data[0] = PRO_OK;
			return;
		}
	}
	pro_buff.data[0] = PRO_FAIL;
}


//==============================================
//获取uid，判断是否支持ISO14443-4，支持就进入
//==============================================
void get_uid(uint8_t *sak)
{
	int8_t status = MI_EMPTY;
	uint8_t cln=0, uid[10];
	uint8_t *p_uid = uid;
	
	memset(fileID, 0, 2);//把上次选择的文件ID清空，防止出错
	auth_flag = 0;//清除外部认证标志
	
	if(pro_check_para(0)) return;//命令长度安全判断

	status = pcd_get_card_uid(&cln, uid, sak);//获取卡片uid，返回联级，uid，和sak
	if(status == MI_OK)
	{
		if((*sak & 0x24) != 0x20)//判断是否符合ISO14443-4协议
		{
			pro_buff.data[0] = PRO_UID_N4;
		}
		else
		{
			status = pcd_rats_a(NULL);//请求ats以进入ISO14443-4
			if(status != MI_OK)
			{
				pro_buff.length = 1;
				pro_buff.data[0] = PRO_ERR_RATS;
			}
			else
			{
				pro_buff.data[0] = PRO_OK;//装填需要返回的数据，第一个是状态码，后面是uid
			}
		}
		pro_buff.length = cln*3+2;//联级大小cln为1-3，uid长度通常有4，7，10三种长度分别对应联级，公式为cln*3+1=(4\7\10)(cln=1\2\3)，返回的还有一个字节状态码所有还要加1
		memcpy(&pro_buff.data[1], p_uid, pro_buff.length-1);
		return;
	}

	pro_buff.length = 1;
	pro_buff.data[0] = PRO_FAIL;
}

//==============================================
//picc下电，判断是否支持ISO14443-4，采用不同的下
//电方式
//==============================================
void stop(uint8_t sak)
{
	int8_t status = MI_EMPTY;
	
	if(pro_check_para(0)) return;//命令长度安全判断
	
	if((sak & 0x24) == 0x20)
	{
		status = pcd_deselect();//deselect
	}
	else
	{
		status = pcd_hlta();//hlta
	}
	pro_buff.length = 1;
	if(status == MI_OK)
	{
		pro_buff.data[0] = PRO_OK;
	}
	else
	{
		pro_buff.data[0] = PRO_FAIL;
	}
}

//==============================================
//外部认证，数据帧的数据部分的格式为两字节目录id
//加八字节密钥
//==============================================
void out_authent(uint8_t *temp)
{
	int8_t status = MI_EMPTY;
	uint8_t des[8];
	pcd_cmd_t pc;
	uint8_t file_id[2];//目录ID
	uint8_t key[8];//密钥
	
	if(pro_check_para(10)) return;//命令长度安全判断
	
	memcpy(file_id, temp, 2);	//前面两个是目录id
	memcpy(key, temp+2, 8);		//后面八个是密钥
	
	pro_select_file(file_id);//选择目录
	if(pro_buff.data[0] != PRO_OK) return;//如果选择目录不成功则退出
	
	if(auth_flag != 0)//前面选择文件不通过不会运行到这里，运行到这说明选择成功或者选择过了，如果选择过了并且外部认证标志不为零说明认证过了不需要再次认证
	{
		pro_buff.data[0] = PRO_OK;//选择文件的时候length已经为1，不用重复
		return;
	}
	
	//获取随机数
	pc.length = 5;
	pc.buff[0] = 0x00;
	pc.buff[1] = 0x84;
	pc.buff[2] = 0x00;
	pc.buff[3] = 0x00;
	pc.buff[4] = 0x08;
	
	status = pcd_cmd_transceive(&pc);
	
	if(status == MI_OK)
	{
		if(((uint16_t)pc.buff[pc.length-2]<<8 | pc.buff[pc.length-1]) == 0x9000)
		{
			//外部认证
			Des(0,pc.buff,key,des);//des加密，比较消耗内存，算力
			pc.length = 13;
			pc.buff[0] = 0x00;
			pc.buff[1] = 0x82;
			pc.buff[2] = 0x00;
			pc.buff[3] = 0x00;
			pc.buff[4] = 0x08;
			memcpy(&pc.buff[5], des, 8);
			
			status = pcd_cmd_transceive(&pc);
			
			if(status == MI_OK)
			{
				if(pc.buff[pc.length-2] == 0x90 && pc.buff[pc.length-1] == 0x00)
				{
					auth_flag = 1;
					pro_buff.length = 1;
					pro_buff.data[0] = PRO_OK;
					return;
				}
				else if(pc.buff[pc.length-2] == 0x6a && pc.buff[pc.length-1] == 0x88)
				{
					pro_buff.length = 1;
					pro_buff.data[0] = PRO_NEXIT_KEY;
					return;
				}
				else if(pc.buff[pc.length-2] == 0x63)//密钥错误，剩余0xcx,x次机会(x=0-f)
				{
					pro_buff.length = 1;
					pro_buff.data[0] = 0x50 | (pc.buff[pc.length-1]&0x0f);
					return;
				}
			}
		}
	}
	pro_buff.length =1;
	pro_buff.data[0] = PRO_FAIL;
}

//==============================================
//卡片初始化，数据帧的数据部分格式为一字节目录数
//量加八字节主密钥加八字节应用密钥
//==============================================
void card_init(uint8_t *temp)
{
	int8_t status = MI_EMPTY;
	uint8_t app_num=0, i;
	uint8_t key_main[8];
	uint8_t key_app[8];
	uint8_t file_id[2];
	uint8_t key_temp[10]={0x3f, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};//0x3f和0x00为MF的目录id，卡片
	
	if(pro_check_para(17)) return;//命令长度安全判断
	
	app_num = *temp;//目录数量
	memcpy(key_main, temp+1, 8);//主密钥
	memcpy(key_app, temp+9, 8);//应用密钥
	
	pro_buff.length = 10;//外部认证里有长度安全判断
	out_authent(key_temp);//在MF下创建文件需要外部认证
	if(pro_buff.data[0] != PRO_OK && pro_buff.data[0] != PRO_NEXIT_KEY) return;
	
	pro_delete();//擦除MF目录下所有文件
	if(pro_buff.data[0] != PRO_OK ) return;//擦除失败
	
	status = pcd_creat_key(key_main);//创建主密钥，密钥需要最先被创建
	if(status != MI_OK) goto fail;
	
	file_id[0] = 0x3f;
	for(i=1;i<=app_num;i++)//如果app_num=0则不创建目录
	{
		file_id[1] = i;
		status = pcd_creat_df(file_id);//循环创建DF目录
		if(status != MI_OK) goto fail;
	}
	
	for(i=1;i<=app_num;i++)//循环在目录创建二进制文件，之所以要分开创建，因为每次选择主目录下创建都需要外部认证，所以先创建完应用目录再到创建文件
	{
		file_id[0] = 0x3f;
		file_id[1] = i;
		pro_select_file(file_id);//选择应用目录
		if(pro_buff.data[0] != PRO_OK) return;
		
		status = pcd_creat_key(key_app);//创建应用密钥，密钥需要最先被创建
		if(status != MI_OK) goto fail;
		
		file_id[0] = 0x00;
		status = pcd_creat_bin(file_id);//创建文件
		if(status != MI_OK) goto fail;
	}
	
	pro_buff.length =1;
	pro_buff.data[0] = PRO_OK;	
	return;
	
fail:
	pro_buff.length =1;
	pro_buff.data[0] = PRO_FAIL;	
}

//==============================================
//卡片回收，数据帧数据部分格式为8字节主密钥
//==============================================
void card_delete(uint8_t *temp)
{
	int8_t status = MI_EMPTY;
	uint8_t key[8]={0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff};//买回来的空白卡片原始的密钥就是8个ff
	uint8_t file_id[2]={0x3f, 0x00};//MF的ID
	uint8_t key_temp[10];
	
	if(pro_check_para(8)) return;//命令长度安全判断
	
	memcpy(key_temp, file_id, 2);
	memcpy(&key_temp[2], temp, 8);
	
	pro_buff.length = 10;//外部认证里有长度安全判断
	out_authent(key_temp);
	if(pro_buff.data[0] != PRO_OK && pro_buff.data[0] != PRO_NEXIT_KEY) return;
	
	pro_delete();//擦除MF目录下所有文件
	if(pro_buff.data[0] != PRO_OK ) return;//擦除失败
	
	pro_buff.length =1;
	status = pcd_creat_key(key);//创建主密钥，密钥需要最先被创建
	if(status == MI_OK)
		pro_buff.data[0] = PRO_OK;
	else
		pro_buff.data[0] = PRO_FAIL;
}

//==============================================
//读二进制文件，格式为目录ID，密钥，偏移，长度
//==============================================
void read_file(uint8_t *temp)
{
	int8_t status = MI_EMPTY;
	uint8_t key_temp[10];
	uint8_t file_id;
	uint8_t offset=0, len=0;
	pcd_cmd_t pc;
	
	if(pro_check_para(12)) return;//命令长度安全判断
	
	file_id = (*(temp+1) & 0x1f) | 0x80;
	memcpy(key_temp, temp, 10);
	offset = *(temp+10);
	len = *(temp+11);
	
	pro_buff.length = 10;//外部认证里有长度安全判断
	out_authent(key_temp);
	if(pro_buff.data[0] != PRO_OK && pro_buff.data[0] != PRO_NEXIT_KEY) return;
	
	pc.length = 5;
	pc.buff[0] = 0x00;
	pc.buff[1] = 0xb0;
	pc.buff[2] = file_id;
	pc.buff[3] = offset;
	pc.buff[4] = len;
	
	pro_buff.length =1;//先给状态为一个长度
	
	status = pcd_cmd_transceive(&pc);
	if(status == MI_OK)
	{
		if(((uint16_t)pc.buff[pc.length-2]<<8 | pc.buff[pc.length-1]) == 0x9000)
		{
			pro_buff.length = pc.length - 1;
			memcpy(&pro_buff.data[1], pc.buff, pc.length-2);
			return;
		}
		else if(((uint16_t)pc.buff[pc.length-2]<<8 | pc.buff[pc.length-1]) == 0x6700)
		{
			pro_buff.data[0] = PRO_ERR_FILELIM;
			return;
		}
		else if(((uint16_t)pc.buff[pc.length-2]<<8 | pc.buff[pc.length-1]) == 0x6a82)
		{
			pro_buff.data[0] = PRO_NEXIT_DIR;
			return;
		}
		else if(((uint16_t)pc.buff[pc.length-2]<<8 | pc.buff[pc.length-1]) == 0x6982)//权限不足
		{
			pro_buff.data[0] = PRO_ERR_LIMIT;
			return;
		}
	}
		
	pro_buff.data[0] = PRO_FAIL;
}

//==============================================
//写二进制文件，格式为目录ID，密钥，偏移，长度
//==============================================
void write_file(uint8_t *temp)
{
	int8_t status = MI_EMPTY;
	uint8_t key_temp[10];
	uint8_t file_id;
	uint8_t offset=0, len=0;
	uint8_t *txt;
	pcd_cmd_t pc;
	
	len = *(temp+11);//可以先赋值，如果这个值加上14长度安全判断对的话那也能说明数据正确
	
	if(pro_check_para(12+len)) return;//命令长度安全判断
	
	file_id = (*(temp+1) & 0x1f) | 0x80;
	memcpy(key_temp, temp, 10);
	offset = *(temp+10);
	txt = malloc(len);
	memcpy(txt, temp+12, len);
	
	pro_buff.length = 10;//外部认证里有长度安全判断
	out_authent(key_temp);
	if(pro_buff.data[0] != PRO_OK && pro_buff.data[0] != PRO_NEXIT_KEY) 
	{
		free(txt);//及时释放内存
		return;
	}
	
	pc.length = 5+len;
	pc.buff[0] = 0x00;
	pc.buff[1] = 0xd6;
	pc.buff[2] = file_id;
	pc.buff[3] = offset;
	pc.buff[4] = len;
	memcpy(&pc.buff[5],txt, len);

	free(txt);
	pro_buff.length =1;

	status = pcd_cmd_transceive(&pc);
	
	if(status == MI_OK)
	{
		if(((uint16_t)pc.buff[pc.length-2]<<8 | pc.buff[pc.length-1]) == 0x9000)
		{
			pro_buff.data[0] = PRO_OK;
			return;
		}
		else if(((uint16_t)pc.buff[pc.length-2]<<8 | pc.buff[pc.length-1]) == 0x6700)
		{
			pro_buff.data[0] = PRO_ERR_FILELIM;
			return;
		}
		else if(((uint16_t)pc.buff[pc.length-2]<<8 | pc.buff[pc.length-1]) == 0x6a82)
		{
			pro_buff.data[0] = PRO_NEXIT_DIR;
			return;
		}
		else if(((uint16_t)pc.buff[pc.length-2]<<8 | pc.buff[pc.length-1]) == 0x6982)//权限不足
		{
			pro_buff.data[0] = PRO_ERR_LIMIT;
			return;
		}
	}

	pro_buff.data[0] = PRO_FAIL;
}

//==============================================
//修改密钥，格式2字节目录id，8字节旧密钥，8字节新密钥
//==============================================
void mod_key(uint8_t *temp)
{
	int8_t status = MI_EMPTY;
	uint8_t key_temp[10];
	uint8_t key_new[8];
	pcd_cmd_t pc;
	
	if(pro_check_para(18)) return;//命令长度安全判断
	
	memcpy(key_temp, temp, 10);
	memcpy(key_new, temp+10, 8);
	
	pro_buff.length = 10;//外部认证里有长度安全判断
	out_authent(key_temp);
	if(pro_buff.data[0] != PRO_OK) return;
	
	pc.length = 18;
	pc.buff[0] = 0x80;
	pc.buff[1] = 0xd4;
	pc.buff[2] = 0x39;
	pc.buff[3] = 0x00;
	pc.buff[4] = 0x0d;
	pc.buff[5] = 0x39;
	pc.buff[6] = 0xf0;
	pc.buff[7] = 0xf5;
	pc.buff[8] = 0xaa;
	pc.buff[9] = 0x88;
	memcpy(&pc.buff[10], key_new, 8);
	
	status = pcd_cmd_transceive(&pc);
	
	if(status == MI_OK)
	{
		if(((uint16_t)pc.buff[pc.length-2]<<8 | pc.buff[pc.length-1]) == 0x9000)
		{
			pro_buff.length = 1;
			pro_buff.data[0] = PRO_OK;
			return;
		}
	}
	pro_buff.length =1;
	pro_buff.data[0] = PRO_FAIL;	
}

void user_cmd(uint8_t *temp)
{
	int8_t status = MI_EMPTY;
	pcd_cmd_t pc;
	
	pc.length = pro_buff.length;//指令的长度
	memcpy(pc.buff, temp, pc.length);//拷贝指令
	
	pro_buff.length = 1;//返回的状态需要一个字节
	status = pcd_cmd_transceive(&pc);//发送指令
	
	if(status == MI_OK)
	{
		pro_buff.length += pc.length;//加上卡片返回数据的长度
		pro_buff.data[0] = PRO_OK;
		memcpy(&pro_buff.data[1], pc.buff, pc.length);//加上卡片返回的数据
	}
	else
	{
		pro_buff.data[0] = PRO_FAIL;
	}
}

